package Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import skyblue.SkyBlueDef;
import skyblue.Variable;
import featuremodel.FeatureGroup;
import featuremodel.FeatureModel;
import featuremodel.FeatureModelDef;
import featuremodel.FeatureRep;

public class RandomFeatureModel {

	private FeatureModel featuremodel;
	private List<List<Variable>> featureForest = new ArrayList<List<Variable>>();
	private int count=0;

//	private ArrayList<Variable> excludeFeatures = new ArrayList<Variable>();
//	private ArrayList<Variable> excludeFeature = new ArrayList<Variable>();

	public RandomFeatureModel() {
		featuremodel = new FeatureModel();

	}

	public void generate(int treeCount, int treeWidth, int treeHeight,
			int requireCount, int excludeCount, int mrequireCount,
			int requireCompositeCount, int excludeCompositeCount,
			int mrequireCompositeCount, int featurePerPredict) {

		this.generateForest(treeCount, treeWidth, treeHeight);
		
		
		this.addRelation(requireCount, FeatureModelDef.REQUIRE_CONSTRAINT);
		this.addRelation(excludeCount, FeatureModelDef.EXCLUDE_CONSTRAINT);
		this.addRelation(mrequireCount, FeatureModelDef.MREQUIRE_CONSTRAINT);
		
		this.addCompositeConstraint(requireCompositeCount, featurePerPredict,
				FeatureModelDef.REQUIRE_COMPLEX_CONSTRAINT,
				FeatureModelDef.PREDICATE_ALL, FeatureModelDef.PREDICATE_MULTI);
		this
				.addCompositeConstraint(requireCompositeCount,
						featurePerPredict,
						FeatureModelDef.REQUIRE_COMPLEX_CONSTRAINT,
						FeatureModelDef.PREDICATE_ALL,
						FeatureModelDef.PREDICATE_SINGLE);
		this.addCompositeConstraint(excludeCompositeCount, featurePerPredict,
				FeatureModelDef.EXCLUDE_COMPLEX_CONSTRAINT,
				FeatureModelDef.PREDICATE_ALL, FeatureModelDef.PREDICATE_MULTI);
		this.addCompositeConstraint(mrequireCompositeCount, featurePerPredict,
				FeatureModelDef.MREQUIRE_COMPLEX_CONSTRAINT,
				FeatureModelDef.PREDICATE_ALL, FeatureModelDef.PREDICATE_MULTI);
		System.out.print("feature number:"+count);
	}

	protected void generateForest(int treeCount, int treeWidth, int treeHeight) {
		Variable f;
		Variable root;
		root = featuremodel.createFeature("root");
		featuremodel.setRoot(root);
		for (int i = 0; i < treeCount; i++) {
			List<Variable> features = new ArrayList<Variable>();
			this.featureForest.add(features);
			f = featuremodel.createFeature(new String(Integer.toString(i)));
			features.add(f);
			count++;
			if (this.random() % 4 == 0) {
				featuremodel.setOptional(f, root,
						SkyBlueDef.MEDIUM_STSTRENGTH);

			} else {
				featuremodel.setMandatory(f, root,
						SkyBlueDef.MEDIUM_STSTRENGTH);

			}
			addChild(f, new String(Integer.toString(i)), treeWidth, treeHeight,
					features);
		}
		
	}

	private void addChild(Variable root, String featureName, int treeWidth,
			int treeHeight, List<Variable> features) {
		//int width =(int)(treeWidth/2+(treeWidth-treeWidth/2)*Math.random());
		treeHeight--;
		if (treeHeight > 0) {
			for (int i = 0; i < treeWidth; i++) {
				Variable feature = featuremodel.createFeature(featureName + "."
						+ Integer.toString(i));
				features.add(feature);
				count++;
				if (root != null) {
					if (this.random() % 4 == 0) {
						featuremodel.setOptional(feature, root,
								SkyBlueDef.MEDIUM_STSTRENGTH);

					} else {
						featuremodel.setMandatory(feature, root,
								SkyBlueDef.MEDIUM_STSTRENGTH);

					}

				}
				addChild(feature, featureName + "." + Integer.toString(i),
						treeWidth, treeHeight, features);
			}
		}

	}

	protected void addRelation(int relationCount, String relationName) {
		//excludeFeature.clear();
		for (int i = 0; i < relationCount; i++) {
			List<Variable> features = this.getRandomFeaturesTwo();
			if (relationName.endsWith(FeatureModelDef.REQUIRE_CONSTRAINT)) {
				featuremodel.addRequireConstraint(features.get(0), features
						.get(1), SkyBlueDef.MEDIUM_STSTRENGTH);

			} else if (relationName
					.endsWith(FeatureModelDef.EXCLUDE_CONSTRAINT)) {
				featuremodel.addExcludeConstraint(features.get(0), features
						.get(1), SkyBlueDef.MEDIUM_STSTRENGTH);
			} else if (relationName
					.endsWith(FeatureModelDef.MREQUIRE_CONSTRAINT)) {
				featuremodel.addMRequireConstraint(features.get(0), features
						.get(1), SkyBlueDef.MEDIUM_STSTRENGTH);
			}

		}
	}

	protected FeatureGroup addGroupPredicate(int featurePerPredict,
			String predicateType) {
		
		FeatureGroup fp;
		Vector<Variable> tempfp = new Vector<Variable>();
		FeatureRep fpp = null;
		List<Variable> features = this.getRandomFeatures(featurePerPredict);
		for (int i = 0; i < features.size(); i++) {
			tempfp.add(features.get(i));
		}
		fp = new FeatureGroup(tempfp, predicateType);
		for (int j = 0; j < features.size(); j++) {
			fpp = (FeatureRep) features.get(j).getValue().getValuerep();
			fpp.getFeatureGroups().add(fp);
		}

		return fp;
	}

	protected void addCompositeConstraint(int compositeCount,
			int featurePerPredict, String compositeConstraintType,
			String type1, String type2) {
		FeatureGroup fp1;
		FeatureGroup fp2;

		for (int i = 0; i < compositeCount; i++) {
			fp1 = addGroupPredicate(featurePerPredict, type1);
			fp2 = addGroupPredicate(featurePerPredict, type2);

			if (compositeConstraintType
					.equals(FeatureModelDef.REQUIRE_COMPLEX_CONSTRAINT)) {
				featuremodel.addRequireCompositeConstraint(fp1, fp1
						.getPredicateType(), fp2, fp2.getPredicateType(),
						SkyBlueDef.MEDIUM_STSTRENGTH);

			} else if (compositeConstraintType
					.equals(FeatureModelDef.EXCLUDE_COMPLEX_CONSTRAINT)) {
				featuremodel.addExcludeCompositeConstraint(fp1, fp1
						.getPredicateType(), fp2, fp2.getPredicateType(),
						SkyBlueDef.MEDIUM_STSTRENGTH);
			} else if (compositeConstraintType
					.equals(FeatureModelDef.MREQUIRE_COMPLEX_CONSTRAINT)) {
				featuremodel.addMRequireCompositeConstraint(fp1, fp1
						.getPredicateType(), fp2, fp2.getPredicateType(),
						SkyBlueDef.MEDIUM_STSTRENGTH);
			}
		}
	}

	private List<Variable> getRandomFeatures(List<Variable> features,
			int featureCount) {
		return getRandomFeaturesExclude(features, featureCount, null);
	}

	private List<Variable> getRandomFeaturesExclude(List<Variable> features,
			int featureCount, ArrayList<Variable> exclude) {
		List<Variable> result = new ArrayList<Variable>();
		while (result.size() < featureCount) {
			int i = this.random() % features.size();
			if (exclude != null && exclude.contains(features.get(i))) {
				continue;
			}
			if (!result.contains(features.get(i))) {
				result.add(features.get(i));
				//excludeFeatures.add(features.get(i));
			}
		}

		return result;
	}

	private Variable getRandomFeature(List<Variable> features) {
		return getRandomFeatureExclude(features, null);
	}

	private Variable getRandomFeatureExclude(List<Variable> features,
			Variable exclude) {
		if (features.size() == 0) {
			return null;
		}
		if (features.size() == 1) {
			return features.get(0);
		} else {
			while (true) {
				int l = (int) (Math.round(Math.random() * Integer.MAX_VALUE) % features.size());
				if (exclude != null && features.get(l).equals(exclude)) {
					continue;
				}
				//excludeFeature.add(features.get(l));
				return features.get(l);
			}
		}
	}

	private List<List<Variable>> apartFeatures = null;

	private List<Variable> getRandomFeatures(int featureCount) {
		if (this.apartFeatures == null) {
			this.apartFeatures = new ArrayList<List<Variable>>();
			this.apartFeatures.add(new ArrayList<Variable>());
			this.apartFeatures.add(new ArrayList<Variable>());

			int size = this.featureForest.size() / 2;
			for (int i = 0; i < size; i++) {
				this.apartFeatures.get(0).addAll(this.featureForest.get(i * 2));
				this.apartFeatures.get(1).addAll(
						this.featureForest.get(i * 2 + 1));
			}
			if (this.featureForest.size() % 2 > 0) {
				this.apartFeatures.get(0).addAll(
						this.featureForest.get(this.featureForest.size() - 1));
			}
		}
		return this.getRandomFeatures(this.apartFeatures.get(this.random()
				% this.apartFeatures.size()), featureCount);
	}

	private List<Variable> twoFeatures = null;

	private List<Variable> getRandomFeaturesTwo() {
		if (this.apartFeatures == null) {
			this.apartFeatures = new ArrayList<List<Variable>>();
			this.apartFeatures.add(new ArrayList<Variable>());
			this.apartFeatures.add(new ArrayList<Variable>());

			int size = this.featureForest.size() / 2;
			for (int i = 0; i < size; i++) {
				this.apartFeatures.get(0).addAll(this.featureForest.get(i * 2));
				this.apartFeatures.get(1).addAll(
						this.featureForest.get(i * 2 + 1));
			}
			if (this.featureForest.size() % 2 > 0) {
				this.apartFeatures.get(0).addAll(
						this.featureForest.get(this.featureForest.size() - 1));
			}
		}
		twoFeatures = new ArrayList<Variable>();
		twoFeatures.add((getRandomFeature(this.apartFeatures.get(0))));
		twoFeatures.add((getRandomFeature(this.apartFeatures.get(1))));
		return twoFeatures;

	}

	private int random() {
		return (int) Math.round(Math.random() * Integer.MAX_VALUE);
	}
}
